﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.ComponentModel.Composition.Hosting;
using RazorGenerator.Mvc;
using System.Reflection;
using System.IO;
using System.Web.WebPages;
using System.Web.Compilation;

namespace PMKB_Web.pluginsCode
{

    public class ViewEngineProvider : VirtualPathProviderViewEngine, IVirtualPathFactory
    {
        private  IDictionary<string, Type> _Mappings;
        

        private string _baseVirtualPath;
        private DateTime _assemblyCreatedTime;
        private readonly string PluginPath;
        public ViewEngineProvider(string path)
            : this(path, null)
        {
        }
        public ViewEngineProvider(string pathPlugin, string baseVirtualPath)
            : base()
        {


            this.PluginPath = pathPlugin;

            base.AreaViewLocationFormats = new[] {
                "~/Areas/{2}/Views/{1}/{0}.cshtml", 
                "~/Areas/{2}/Views/Shared/{0}.cshtml", 
            };

            base.AreaMasterLocationFormats = new[] {
                "~/Areas/{2}/Views/{1}/{0}.cshtml", 
                "~/Areas/{2}/Views/Shared/{0}.cshtml", 
            };

            base.AreaPartialViewLocationFormats = new[] {
                "~/Areas/{2}/Views/{1}/{0}.cshtml", 
                "~/Areas/{2}/Views/Shared/{0}.cshtml", 
            };
            base.ViewLocationFormats = new[] {
                "~/Views/{1}/{0}.cshtml", 
                "~/Views/Shared/{0}.cshtml", 
            };
            base.MasterLocationFormats = new[] {
                "~/Views/{1}/{0}.cshtml", 
                "~/Views/Shared/{0}.cshtml", 
            };
            base.PartialViewLocationFormats = new[] {
                "~/Views/{1}/{0}.cshtml", 
                "~/Views/Shared/{0}.cshtml", 
            };
            base.FileExtensions = new[] {
                "cshtml", 
            };

            _Mappings = new Dictionary<string, Type>();
            //TODO :mef
            try
            {


                foreach (var file in Directory.EnumerateFiles(PluginPath, "plugin*.dll"))
                {
                    string PathFile = Path.Combine(PluginPath, file);
                    var assembly = Assembly.LoadFrom(PathFile);



                    _assemblyCreatedTime = File.GetLastWriteTimeUtc(assembly.Location);
                    _baseVirtualPath = NormalizeBaseVirtualPath(baseVirtualPath);

                    var col = (from type in assembly.GetTypes()
                               where typeof(WebViewPage).IsAssignableFrom(type)
                               let pageVirtualPath = type.GetCustomAttributes(inherit: false).OfType<PageVirtualPathAttribute>().FirstOrDefault()
                               where pageVirtualPath != null
                               select new KeyValuePair<string, Type>(CombineVirtualPaths(_baseVirtualPath, pageVirtualPath.VirtualPath), type)
                          ).ToDictionary(t => t.Key.ToLowerInvariant(), t => t.Value, StringComparer.OrdinalIgnoreCase);

                    if (col.Count > 0)
                    {
                        foreach (var item in col)
                        {
                            _Mappings.Add(item);
                        }
                    }

                }
            }
            catch (System.IO.DirectoryNotFoundException)
            {
                //TODO: log
                
            }


        }
        /// <summary>
        /// Determines if IVirtualPathFactory lookups returns files from assembly regardless of whether physical files are available for the virtual path.
        /// </summary>
        public bool PreemptPhysicalFiles
        {
            get;
            set;
        }

        /// <summary>
        /// Determines if the view engine uses views on disk if it's been changed since the view assembly was last compiled.
        /// </summary>
        /// <remarks>
        /// What an awful name!
        /// </remarks>
        public bool UsePhysicalViewsIfNewer
        {
            get;
            set;
        }

        protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
        {
            if (UsePhysicalViewsIfNewer && IsPhysicalFileNewer(virtualPath))
            {
                // If the physical file on disk is newer and the user's opted in this behavior, serve it instead.
                return false;
            }
            return Exists(virtualPath);
        }

        protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
        {
            return CreateViewInternal(partialPath, runViewStartPages: false);
        }

        protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
        {
            return CreateViewInternal(viewPath, runViewStartPages: true);
        }

        private IView CreateViewInternal(string viewPath, bool runViewStartPages)
        {
            Type type;
            if (_Mappings.TryGetValue(viewPath.ToLowerInvariant(), out type))
            {                
                return new PrecompiledMvcView(viewPath, type, runViewStartPages, base.FileExtensions);
            }
            return null;
        }

        public object CreateInstance(string virtualPath)
        {
            Type type;

            if (!PreemptPhysicalFiles && VirtualPathProvider.FileExists(virtualPath))
            {
                // If we aren't pre-empting physical files, use the BuildManager to create _ViewStart instances if the file exists on disk. 
                return BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(WebPageRenderingBase));
            }

            if (UsePhysicalViewsIfNewer && IsPhysicalFileNewer(virtualPath))
            {
                // If the physical file on disk is newer and the user's opted in this behavior, serve it instead.
                return BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(WebViewPage));
            }

            if (_Mappings.TryGetValue(virtualPath.ToLowerInvariant(), out type))
            {
                return Activator.CreateInstance(type);
            }
            return null;
        }

        public bool Exists(string virtualPath)
        {
            bool exists=_Mappings.ContainsKey(virtualPath.ToLowerInvariant());
            return exists;

        }

        private bool IsPhysicalFileNewer(string virtualPath)
        {
            if (virtualPath.StartsWith(_baseVirtualPath ?? String.Empty, StringComparison.Ordinal))
            {
                // If a base virtual path is specified, we should remove it as a prefix. Everything that follows should map to a view file on disk.
                if (!String.IsNullOrEmpty(_baseVirtualPath))
                {
                    virtualPath = '~' + virtualPath.Substring(_baseVirtualPath.Length);
                }

                string path = HttpContext.Current.Request.MapPath(virtualPath);
                return File.Exists(path) && File.GetLastWriteTimeUtc(path) > _assemblyCreatedTime;
            }
            return false;
        }

        private static string NormalizeBaseVirtualPath(string virtualPath)
        {
            if (!String.IsNullOrEmpty(virtualPath))
            {
                // For a virtual path to combine properly, it needs to start with a ~/ and end with a /.
                if (!virtualPath.StartsWith("~/", StringComparison.Ordinal))
                {
                    virtualPath = "~/" + virtualPath;
                }
                if (!virtualPath.EndsWith("/", StringComparison.Ordinal))
                {
                    virtualPath += "/";
                }
            }
            return virtualPath;
        }

        private static string CombineVirtualPaths(string baseVirtualPath, string virtualPath)
        {
            if (!String.IsNullOrEmpty(baseVirtualPath))
            {
                return VirtualPathUtility.Combine(baseVirtualPath, virtualPath.Substring(2));
            }
            return virtualPath;
        }
    }
}